﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Net;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Net.Http.Headers;
using System.Text;
using WooCoo.Data;
using WooCoo.DataPortal.Client;
using WooCoo.ObjectModel;
using WooCoo.ObjectModel.ManagedProperty;
using WooCoo.Tools;

namespace WooCoo.DataPortal.Client
{
    /// <summary>
    /// 采用OData协议的数据门户
    /// </summary>
    /// <remarks>目前支持的为Json格式</remarks>
    public class ODataProxy : IDataPortalProxy
    {
        /// <summary>
        /// 获取或设置每次请求的身份信息
        /// </summary>
        public System.Net.ICredentials Credentials { get; set; }

        private int timeout;
        /// <summary>
        /// 获取或设置超时时间
        /// </summary>        
        public int Timeout
        {
            get
            {
                return this.timeout;
            }

            set
            {
                if (value < 0)
                {
                    throw Error.ArgumentOutOfRange("Timeout");
                }

                this.timeout = value;
            }
        }

        /// <summary>是否PUT/DELETE使用Post通道</summary>
        public bool UsePostTunneling { get; set; }

        /// <summary>Same version as <see cref="maxProtocolVersion"/> but stored as instance of <see cref="Version"/> for easy comparisons.</summary>
        internal Version MaxProtocolVersionAsVersion;

        public static MediaTypeFormatter DefaultMediaTypeFormatter { get; set; }

        public bool IsServerRemote
        {
            get { return true; }
        }
        public Uri BaseAddress { get; set; }

        static ODataProxy()
        {
            var jf = new JsonMediaTypeFormatter();
            jf.UseDataContractJsonSerializer = false;
            jf.SerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore;
            DefaultMediaTypeFormatter = jf;
        }
        public ODataProxy()
        {
            this.UsePostTunneling = false;
            this.Timeout = 0;


            //this.DefaultMediaTypeFormatter = jf;
        }

        public ODataProxy(ODataProtocolVersion maxProtocolVersion)
        {
            this.MaxProtocolVersionAsVersion = ODataVersionTools.GetVersionFromMaxProtocolVersion(maxProtocolVersion);
        }

        /// <summary>
        /// OData的Http Get方法
        /// </summary>
        /// <param name="objectType">请求的类型</param>
        /// <param name="criteria">查询条件，在Odata中为<see cref="WooCoo.DataPortal.Client.QueryComponents"/></param>
        /// <returns></returns>
        public DataPortalResult Fetch<T>(object criteria)
        {
            if (!(criteria is QueryComponents))
            {
                throw Error.NotSupported(StringID.UnsupportedExpression.ToLocalString());
            }
            var queryComponets = criteria as QueryComponents;
            using (HttpClient request = this.CreateRequest(queryComponets.Uri, "GET", false, null, queryComponets.Version, false))
            {
                var resp = request.GetAsync("");
                if (resp.Result.StatusCode == System.Net.HttpStatusCode.OK)
                {
                    string resultStr = resp.Result.Content.ReadAsStringAsync().Result;
                    if (queryComponets.SingleResult.HasValue && queryComponets.SingleResult.Value)
                    {
                        //单实体请求，一般为带主键请求或者$top=1请求
                        var container = new ODataSampleFormat<T>();
                        var item = Newtonsoft.Json.JsonConvert.DeserializeObject<T>(resultStr);
                        container.Values.Add(item);
                        return new DataPortalResult(container.Values);
                    }
                    else
                    {
                        //请求为列表
                        var result = Newtonsoft.Json.JsonConvert.DeserializeObject<ODataSampleFormat<T>>(resultStr);
                        return new DataPortalResult(result.Values);
                    }
                }
                else if (resp.Result.StatusCode == HttpStatusCode.NotFound)
                {
                    //未得到查询结果时，服务商返回404
                    //var listType = typeof(System.Collections.ObjectModel.Collection<>).MakeGenericType(objectType);
                    IList al = new List<T>();//(IList)System.Activator.CreateInstance(listType);                    
                    return new DataPortalResult(al);
                }
                else
                {
                    throw ConvertToException(resp.Result);
                    //var ex = resp.Result.Content.ReadAsStringAsync().Result;
                    //throw new Exception(ex);
                }
            }
        }
        /// <summary>
        /// Post请求，一般对应Insert方式。
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public DataPortalResult Create<T>(object entityOrList)
        {
            //ActionInfo ai = this.CreateEntityActionInfo(obj,"Post");
            string controller = typeof(T).Name;
            using (HttpClient request = this.CreateRequest(this.BaseAddress, "Post", false, null, this.MaxProtocolVersionAsVersion, false))
            {
                Uri postUri = UriHelper.CreateUri(this.BaseAddress, UriHelper.CreateUri(controller, UriKind.Relative));

                //HttpContent content = new ObjectContent<ActionInfo>(ai, ODataProxy.DefaultMediaTypeFormatter);
                var resp = request.PostAsync(postUri.ToString(), entityOrList, ODataProxy.DefaultMediaTypeFormatter);
                if (resp.Result.StatusCode == System.Net.HttpStatusCode.OK)
                {
                    return new DataPortalResult(entityOrList);
                }
                else
                {
                    throw ConvertToException(resp.Result);
                    //var ex = resp.Result.Content.ReadAsAsync<ODataErrorFormat>().Result;
                    //throw this.ConvertToException(ex);
                }
            }
        }

        public DataPortalResult Update<T>(object entityOrList)
        {
            string controller = typeof(T).Name;
            using (HttpClient request = this.CreateRequest(this.BaseAddress, "Put", false, null, this.MaxProtocolVersionAsVersion, false))
            {
                Uri postUri = UriHelper.CreateUri(this.BaseAddress, UriHelper.CreateUri(controller, UriKind.Relative));

                //HttpContent content = new ObjectContent<ActionInfo>(ai, ODataProxy.DefaultMediaTypeFormatter);
                var resp = request.PutAsync(postUri.ToString(), entityOrList, ODataProxy.DefaultMediaTypeFormatter);
                if (resp.Result.StatusCode == System.Net.HttpStatusCode.OK)
                {
                    return new DataPortalResult(entityOrList);
                }
                else
                {
                    throw ConvertToException(resp.Result);
                    //var ex = resp.Result.Content.ReadAsAsync<ODataErrorFormat>().Result;
                    //throw this.ConvertToException(ex);
                }
            }
        }

        public DataPortalResult Delete<T>(object obj)
        {
            string controller = typeof(T).Name;
            if(obj is ITrackStatus)
            {
                //具有状态跟踪
                return Update<T>(obj);
            }

            EntityKeyAttribute dska = typeof(T).GetCustomAttributes(true).OfType<EntityKeyAttribute>().FirstOrDefault();
            var pinfo = PropertyInfoRepository.Instance.GetPropertyInfoSet(typeof(T));
            foreach (var item in pinfo.GetProperties())
            {
                if(dska!=null && dska.KeyNames.Contains(item.Name))
                {
                    controller += "("+(obj as Entity).GetProperty(item).ToString()+")";
                    break;
                }
            }
            using (HttpClient request = this.CreateRequest(this.BaseAddress, "Delete", false, null, this.MaxProtocolVersionAsVersion, false))
            {
                Uri postUri = UriHelper.CreateUri(this.BaseAddress, UriHelper.CreateUri(controller, UriKind.Relative));

                //HttpContent content = new ObjectContent<ActionInfo>(ai, ODataProxy.DefaultMediaTypeFormatter);
                var resp = request.DeleteAsync(postUri.ToString());
                if (resp.Result.StatusCode == System.Net.HttpStatusCode.OK)
                {
                    return new DataPortalResult(obj);
                }
                else
                {
                    throw ConvertToException(resp.Result);
                    //var ex = resp.Result.Content.ReadAsAsync<ODataErrorFormat>().Result;
                    //throw this.ConvertToException(ex);
                }
            }
        }

        public Client.QueryComponents Translate(System.Linq.Expressions.Expression e)
        {
            bool addTrailingParens = false;
            Dictionary<Expression, Expression> dictionary = null;
            if (!(e is ResourceSetExpression))
            {
                dictionary = new Dictionary<Expression, Expression>(ReferenceEqualityComparer<Expression>.Instance);
                e = Evaluator.PartialEval(e);
                e = ExpressionNormalizer.Normalize(e, dictionary);
                e = ResourceBinder.Bind(e);
                addTrailingParens = true;
            }
            Uri uri;
            Version version;
            UriWriter.Translate(this, addTrailingParens, e, out uri, out version);
            ResourceExpression resourceExpression = e as ResourceExpression;
            bool isSingle = resourceExpression.IsSingleton;
            Type lastSegmentType = (resourceExpression.Projection == null) ? resourceExpression.ResourceType : resourceExpression.Projection.Selector.Parameters[0].Type;
            LambdaExpression projection = (resourceExpression.Projection == null) ? null : resourceExpression.Projection.Selector;
            return new QueryComponents(uri, version, lastSegmentType, projection, dictionary, "Get", isSingle, null, null
                );
        }

        public DataPortalResult Execute(ActionInfo requestMethod)
        {
            using (HttpClient request = this.CreateRequest(this.BaseAddress, "Post", false, null, this.MaxProtocolVersionAsVersion, false))
            {
                var resp = request.PostAsync(this.BaseAddress.ToString(), requestMethod, ODataProxy.DefaultMediaTypeFormatter);
                if (resp.Result.StatusCode == System.Net.HttpStatusCode.OK)
                {
                    return resp.Result.Content.ReadAsAsync<DataPortalResult>().Result;
                    //return new DataPortalResult(entityOrList);
                }
                else
                {
                    throw ConvertToException(resp.Result);
                    //var ex = resp.Result.Content.ReadAsAsync<ODataErrorFormat>().Result;
                    //throw this.ConvertToException(ex);
                }
            }
        }

        public T DeserializeObject<T>(DataPortalResult sObject)
        {
            if (sObject.ReturnObject is Newtonsoft.Json.Linq.JContainer)
                return Newtonsoft.Json.JsonConvert.DeserializeObject<T>(sObject.ReturnObject.ToString());
            //else if(sObject.Result is Newtonsoft.Json.Linq.j
            if (sObject.ReturnObject == null)
                return default(T);
            return TypeHelper.CoerceValue<T>(sObject.ReturnObject);

        }

        /// <summary>
        /// 创建Http请求
        /// </summary>
        /// <param name="requestUri">请求的Rri</param>
        /// <param name="method">http method</param>
        /// <param name="allowAnyType">Whether the request/response should request/assume ATOM or any MIME type</param>
        /// <param name="contentType">content type for the request. Can be null which means don't set the header.</param>
        /// <param name="requestVersion">请求版本，服务端返回的将根据版本信息返回相应的消息格式。目前为4.0</param>
        /// <param name="sendChunked">Set to true if the request body should be sent using chunked encoding.</param>
        /// <returns>a request ready to get a response</returns>
        internal HttpClient CreateRequest(Uri requestUri, string method, bool allowAnyType, string contentType, Version requestVersion, bool sendChunked)
        {
            Debug.Assert(null != requestUri, "request uri is null");
            Debug.Assert(requestUri.IsAbsoluteUri, "request uri is not absolute uri");
            Debug.Assert(
                requestUri.Scheme.Equals("http", StringComparison.OrdinalIgnoreCase) ||
                    requestUri.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase),
                "request uri is not for HTTP");

            #region handler
            HttpClientHandler handle = new HttpClientHandler();
            if (null != this.Credentials)
            {
                handle.Credentials = this.Credentials;
            }
            handle.AutomaticDecompression = System.Net.DecompressionMethods.GZip | System.Net.DecompressionMethods.Deflate;
            #endregion

            HttpClient client = new HttpClient(handle);
            //(HttpWebRequest)WebRequest.Create(requestUri);
            client.BaseAddress = requestUri;            
            client.DefaultRequestHeaders.Add("UserAgent", "WooCoo DataPortal");
            client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(XmlConstants.MimeApplicationJson));
            client.DefaultRequestHeaders.AcceptCharset.Add(new StringWithQualityHeaderValue(XmlConstants.Utf8Encoding));
            // Always sending the version along allows the server to fail before processing.
            // devnote([....]): this needs to be set before SendingRequest is fired, so client has a chance to modify them
            if (requestVersion != null && requestVersion.Major > 0)
            {
                // if request version is 0.x, then we don't put a DSV header 
                // in this case it's up to the server to decide what version this request is
                client.DefaultRequestHeaders.Add(XmlConstants.HttpODataVersion, requestVersion.ToString() + ";NetFx");
            }
            if (0 != this.timeout)
            {
                client.Timeout = new TimeSpan(0, 0, this.timeout);
            }
            //client.DefaultRequestHeaders.Add(XmlConstants.HttpMaxDataServiceVersion, Util.MaxResponseVersion.ToString() + Util.VersionSuffix);
            return client;
        }
        protected virtual ActionInfo CreateEntityActionInfo(object obj, string HttpMethod)
        {
            Check.CheckArgumentNull(obj, "obj");
            ActionInfo result = new ActionInfo();
            //将元素类型定义为contractName
            result.ContractName = TypeHelper.GetElementType(obj.GetType()).Name;
            //通过MVC的路由机制，在服务端取得ActionName
            result.MethodName = HttpMethod;

            //是否枚举
            if ((obj as IEnumerable) == null)
            {
                result.Parameters = new object[1];
                result.Parameters[0] = obj;
            }
            else
            {

                var list = new List<object>();
                var objIE = (obj as IEnumerable).GetEnumerator();
                while (objIE.MoveNext())
                {
                    list.Add(objIE.Current);
                }
                result.Parameters = list.ToArray();
            }
            return result;

        }
        private Exception ConvertToException(HttpResponseMessage resp)
        {
            try
            {
                var oef = resp.Content.ReadAsAsync<ODataErrorFormat>().Result;
                if (oef.ODataError.InnerError.InternalException != null)
                {
                    return new DataPortalException(oef.ODataError.InnerError.Message, oef.ODataError.InnerError.StackTrace,
                        new DataPortal.DataPortalException(oef.ODataError.InnerError.InternalException.Message, oef.ODataError.InnerError.InternalException.StackTrace));
                }
                return new DataPortalException(oef.ODataError.InnerError.Message, oef.ODataError.InnerError.StackTrace);
            }catch
            {
                return new Exception(resp.Content.ReadAsStringAsync().Result);
            }
        }
    }
}
